package com.db.log.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;

import com.db.log.index.IndexSession;
import com.db.log.index.Indexer;

public class RegExFileParser implements FileParser {

	private RegExParserConfig fileConfig;
	
	private RegExParserConfig lineConfig;

	
	public void setFileConfig(RegExParserConfig fileConfig) {
		this.fileConfig = fileConfig;
	}

	public void setLineConfig(RegExParserConfig lineConfig) {
		this.lineConfig = lineConfig;
	}



	@Override
	public boolean parse(File file, Indexer indexer) throws ParserException {
		
		Matcher fileMatcher = findMatcher(file.getName(), fileConfig);

		if (fileMatcher == null) {
			return false;
		}
		
		IndexSession session = indexer.createSession();
		
		try {
			BufferedReader reader = null;
			
				reader =
					new BufferedReader(
						new InputStreamReader(
							getInputStream(file)
						)
					);
	
			Matcher previousLineMatcher = null;
			String previousMatchedLine = null;
			
			StringBuffer unmatchedLinesBuffer = new StringBuffer();
			
			String currentLine = null;
			while ((currentLine = reader.readLine()) != null) {
				Matcher currentLineMatcher = findMatcher(currentLine, lineConfig);
				
				if (currentLineMatcher != null) {
					index(
						fileMatcher,
						previousLineMatcher,
						previousMatchedLine,
						unmatchedLinesBuffer, 
						session
					);
					
					previousMatchedLine = currentLine;
					previousLineMatcher = currentLineMatcher;
					unmatchedLinesBuffer = new StringBuffer();
				}
				
				else {
					if (currentLine.trim().length() > 0) {
						if (unmatchedLinesBuffer.length() > 0) {
							unmatchedLinesBuffer.append(" ");
						}
						unmatchedLinesBuffer.append(currentLine);
					}
					
					currentLineMatcher = findMatcher(unmatchedLinesBuffer, lineConfig);
					if (currentLineMatcher != null) {
						
						index(
							fileMatcher,
							previousLineMatcher,
							previousMatchedLine,
							new StringBuffer(), 
							session
						);
						
						previousMatchedLine = unmatchedLinesBuffer.toString();
						previousLineMatcher = currentLineMatcher;
						unmatchedLinesBuffer = new StringBuffer();
					}
				}
			}
			
			index(
				fileMatcher,
				previousLineMatcher,
				previousMatchedLine,
				unmatchedLinesBuffer, 
				session
			);
		}
		
		catch (Exception e) {
			throw new ParserException(e);
		}
		
		finally {
			session.close();
		}
		
		return true;
	}
	
	
	
	private void index(Matcher fileMatcher, Matcher previousLineMatcher, String previousMatchedLine, StringBuffer unmatchedLinesBuffer, IndexSession session) {
		if (previousMatchedLine != null && previousLineMatcher != null) {
			if (unmatchedLinesBuffer.length() > 0) {
				String compositeLine = previousMatchedLine + " " + unmatchedLinesBuffer;
				Matcher compositeLineMatcher = findMatcher(compositeLine, lineConfig);
				
				if (compositeLineMatcher != null) {
					index(fileMatcher, compositeLineMatcher, session);
				}
				else {
					index(fileMatcher, previousLineMatcher, session);
				}
			}
			else {
				index(fileMatcher, previousLineMatcher, session);
			}
		}
	}
	
	
	
	private Matcher findMatcher(CharSequence text, RegExParserConfig config) {
		if (config == null || config.getPatterns() == null) {
			return null;
		}
		
		for (Pattern pattern : config.getPatterns()) {
			Matcher matcher = pattern.matcher(text);
			if (matcher.matches()) {
				return matcher;
			}
		}
		
		return null;
	}
	
	
	
	private void index(Matcher fileMatcher, Matcher lineMatcher, IndexSession session) {
		
		SortedMap<Field, String> fieldValues = new TreeMap<Field, String>();
		StringBuffer keyBuffer = new StringBuffer();
		
		extractValues(
			fileMatcher, 
			fileConfig, 
			fieldValues, 
			keyBuffer
		);
		
		extractValues(
			lineMatcher, 
			lineConfig, 
			fieldValues, 
			keyBuffer
		);
		
		session.startDocument(keyBuffer.toString());
		
		for (Map.Entry<Field, String> entry : fieldValues.entrySet()) {
			session.addField(
				entry.getKey().getName(), 
				entry.getValue(), 
				(entry.getKey().getFormat() != null) ? new SimpleDateFormat(entry.getKey().getFormat()) : null
			);
		}
		
		session.endDocument();
	}
	
	
	
	private void extractValues(Matcher matcher, RegExParserConfig config, Map<Field, String> fieldValues, StringBuffer keyBuffer) {
		if (config.getFields() != null) {
			for (Field field : config.getFields()) {
				
				fieldValues.put(
					field, 
					matcher.group(field.getGroup())
				);
				
				if (field.isKey()) {
					keyBuffer.append("/").append(field.getName());
				}
			}
		}
	}
	
	
	
	private InputStream getInputStream(File file) throws IOException {
		FileInputStream in = new FileInputStream(file);
		if (file.getName().toLowerCase().endsWith(".gz")) {
			return new GZIPInputStream(in);
		}
		return in;
	}

}
